package com.jwater.core;

import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;

import com.jwater.core.manager.common.DataKeys;
import com.jwater.core.manager.common.model.JobInfo;
import com.jwater.core.manager.common.model.WorkerInfo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.jwater.core.common.InitLogger;
import com.jwater.core.common.JvmNames;
import com.jwater.core.common.UsageException;
import com.jwater.core.manager.common.conf.CoreConfParse;
import com.jwater.core.manager.common.deploy.DeployUtils;
import com.jwater.core.manager.common.model.NodeInfo;
import com.jwater.core.manager.common.service.JwaterService;
import com.jwater.core.manager.job.JobMain;
import com.jwater.core.manager.master.MasterMain;
import com.jwater.core.manager.proxy.ProxyClient;
import com.jwater.core.manager.proxy.ProxyMain;
import com.jwater.core.manager.proxy.ProxyManager;
import com.jwater.core.manager.worker.WorkerMain;
import com.jwater.core.metrics.SystemMetric;
import com.jwater.core.net.NetExecutorUtils;
import com.jwater.core.net.SimpleNode;
import com.jwater.core.utils.ProcessUtils;
import com.jwater.core.utils.ProcessUtils.ProcessCallback;
import com.jwater.core.utils.ResourceUtils;
import com.jwater.core.utils.SysType;
import com.jwater.launch.HomePath;
import com.jwater.launch.Jwater;

import jwater.org.apache.commons.io.FileUtils;
import jwater.org.apache.commons.io.IOUtils;
import jwater.org.springframework.util.StringUtils;

/**
 * @description:启动器入口
 * @author: jnner
 * @createDate: 2021/2/5
 * @version: 1.0
 */
public class Bootstrap {
    protected static Logger logger = LoggerFactory.getLogger(Bootstrap.class);

    public static void main(String[] args) throws Exception {
        InitLogger.initBootstrap();
        logger.info("args:" + Arrays.toString(args));
        try {
            checkArgs(args, 1);
            boolean printConsole = false;
            if ("-c".equals(args[0])) {
                printConsole = true;
                args = toArgs(args);
            }
            String opera = args[0];
            if ("start".equalsIgnoreCase(opera)) {
                operaForStart(args, printConsole);
            } else if ("stop".equalsIgnoreCase(opera)) {
                operaForStop(args);
            } else if ("run".equalsIgnoreCase(opera)) {
                operaForRun(args, printConsole);
            } else if ("kill".equalsIgnoreCase(opera)) {
                operaForKill(args);
            } else if ("deploy".equalsIgnoreCase(opera)) {
                operaForDeploy(args);
            } else if ("remove".equalsIgnoreCase(opera)) {
                operaForRemove(args);
            } else if ("loadConf".equalsIgnoreCase(opera)) {
                operaForLoadConf(args);
            } else if ("listWorker".equalsIgnoreCase(opera)) {
                operaForListWorker(args);
            } else if ("listJob".equalsIgnoreCase(opera)) {
                operaForListJob(args);
            } else {
                printUsage();
            }
        } catch (UsageException e) {
            printUsage();
        }
    }

    /**
     * 转化参数
     *
     * @param args
     * @return
     */
    private static String[] toArgs(String[] args) {
        String[] newArgs = new String[args.length - 1];
        for (int i = 1; i < args.length; i++) {
            newArgs[i - 1] = args[i];
        }
        return newArgs;
    }

    /**
     * 检查参数个数
     *
     * @param args
     * @param length
     */
    public static void checkArgs(String[] args, int length) {
        if (args.length < length) {
            throw new UsageException();
        }
    }

    /**
     * 输出命令说明
     *
     * @throws Exception
     */
    public static void printUsage() throws Exception {
        String fileName = "usage-linux.txt";
        if (SysType.WINDOWS.equals(SysType.getSysType())) {
            fileName = "usage-windows.txt";
        }
        InputStream is = ResourceUtils.getResource("classpath*:/usage/" + fileName).getInputStream();
        IOUtils.readLines(is).forEach(System.out::println);
        is.close();
    }

    public static ProcessCallback toProcessCallback(boolean printConsole) {
        if (printConsole) {
            return new ProcessCallback() {

                @Override
                public void onStop() {
                }

                @Override
                public void onMsg(String msg) {
                    System.out.println(msg);
                }

                @Override
                public void onErrorMsg(String msg) {
                    System.err.println(msg);
                }
            };
        } else {
            return null;
        }
    }

    /**
     * start操作
     *
     * @param args
     * @param printConsole
     * @throws Exception
     */
    public static void operaForStart(String[] args, boolean printConsole) throws Exception {
        checkArgs(args, 2);
        String type = args[1];
        if ("console".equalsIgnoreCase(type)) {
            startConsole(printConsole);
        } else if ("master".equalsIgnoreCase(type)) {
            checkArgs(args, 3);
            startMaster(Integer.parseInt(args[2]), printConsole);
        } else if ("proxy".equalsIgnoreCase(type)) {
            startProxy(printConsole);
        } else if ("worker".equalsIgnoreCase(type)) {
            checkArgs(args, 7);
            String regionName = args[2];
            String deployName = args[3];
            String groupName = args[4];
            String workerName = args[5];
            int index = Integer.parseInt(args[6]);
            startWorker(regionName, deployName, groupName, workerName, index);
        } else if ("job".equalsIgnoreCase(type)) {
            checkArgs(args, 6);
            String regionName = args[2];
            String deployName = args[3];
            String groupName = args[4];
            String jobName = args[5];
            startJob(regionName, deployName, groupName, jobName);
        } else {
            printUsage();
        }
    }

    /**
     * stop操作
     *
     * @param args
     * @throws Exception
     */
    public static void operaForStop(String[] args) throws Exception {
        checkArgs(args, 2);
        String type = args[1];
        if ("console".equalsIgnoreCase(type)) {
            stopConsole();
        } else if ("master".equalsIgnoreCase(type)) {
            checkArgs(args, 3);
            stopMaster(Integer.parseInt(args[2]));
        } else if ("proxy".equalsIgnoreCase(type)) {
            stopProxy();
        } else if ("worker".equalsIgnoreCase(type)) {
            checkArgs(args, 7);
            String regionName = args[2];
            String deployName = args[3];
            String groupName = args[4];
            String workerName = args[5];
            int index = Integer.parseInt(args[6]);
            stopWorker(regionName, deployName, groupName, workerName, index);
        } else if ("job".equalsIgnoreCase(type)) {
            checkArgs(args, 6);
            String regionName = args[2];
            String deployName = args[3];
            String groupName = args[4];
            String jobName = args[5];
            stopJob(regionName, deployName, groupName, jobName);
        } else {
            printUsage();
        }
    }

    /**
     * run操作
     *
     * @param args
     * @param printConsole
     * @throws Exception
     */
    public static void operaForRun(String[] args, boolean printConsole) throws Exception {
        checkArgs(args, 2);
        String type = args[1];
        if ("worker".equalsIgnoreCase(type)) {
            checkArgs(args, 7);
            String regionName = args[2];
            String deployName = args[3];
            String groupName = args[4];
            String workerName = args[5];
            int index = Integer.parseInt(args[6]);
            String jvmArgs = "-Xmx1g";
            if (args.length > 7) {
                jvmArgs = args[7];
            }
            Map<String, String> envs = DeployUtils.getWorkerEnvs(regionName, deployName, groupName, workerName);
            runWorker(null, regionName, deployName, groupName, workerName, index, jvmArgs, printConsole, (map) -> {
                // 获取worker配置文件，解析是否有环境变量，有的话设置环境变量
                for (Map.Entry<String, String> en : envs.entrySet()) {
                    map.put(en.getKey(), en.getValue());
                }
            });
        } else if ("job".equalsIgnoreCase(type)) {
            checkArgs(args, 7);
            String regionName = args[2];
            String deployName = args[3];
            String groupName = args[4];
            String jobName = args[5];
            long time = Long.parseLong(args[6]);
            String jvmArgs = "-Xmx1g";
            if (args.length > 7) {
                jvmArgs = args[7];
            }
            runJob(regionName, deployName, groupName, jobName, time, jvmArgs, printConsole);
        } else {
            printUsage();
        }
    }

    /**
     * kill操作
     *
     * @param args
     * @throws Exception
     */
    public static void operaForKill(String[] args) throws Exception {
        checkArgs(args, 2);
        String type = args[1];
        if ("worker".equalsIgnoreCase(type)) {
            checkArgs(args, 7);
            String regionName = args[2];
            String deployName = args[3];
            String groupName = args[4];
            String workerName = args[5];
            int index = Integer.parseInt(args[6]);
            killWorker(regionName, deployName, groupName, workerName, index, false);
        } else if ("job".equalsIgnoreCase(type)) {
            checkArgs(args, 6);
            String regionName = args[2];
            String deployName = args[3];
            String groupName = args[4];
            String jobName = args[5];
            killJob(regionName, deployName, groupName, jobName);
        } else {
            printUsage();
        }
    }

    /**
     * 部署安装包
     *
     * @param args
     * @throws Exception
     */
    public static void operaForDeploy(String[] args) throws Exception {
        checkArgs(args, 3);
        File file = new File(args[1]);
        String regionName = args[2];
        if (!file.exists()) {
            logger.error("can not find file for:" + args[1]);
        }
        CoreConfParse coreConfParse = new CoreConfParse();
        coreConfParse.parse();
        List<NodeInfo> nodeInfos = coreConfParse.getNodeList();
        long version = System.currentTimeMillis();
        for (NodeInfo nodeInfo : nodeInfos) {
            if (SystemMetric.isLocalHost(nodeInfo.getHost())) {
                String dst = HomePath.getRemoteInstallFilePath(HomePath.getHomePath(), regionName, file.getName());
                FileUtils.copyFile(file, new File(dst));
                DeployUtils.deploy(file.getName(), dst, regionName, null, "true", version, "");
            } else {
                String homePath = HomePath.getRemoteHomePath(nodeInfo.getPath());
                String dst = HomePath.getRemoteInstallFilePath(homePath, regionName, file.getName());
                ProxyManager proxyManager = new ProxyManager(coreConfParse.getProxyInfos());
                proxyManager.start(coreConfParse);
                ProxyClient proxyClient = proxyManager.getProxyClient(nodeInfo.getHost());
                if (proxyClient != null && proxyClient.isConnect()) {
                    FileInputStream fis = new FileInputStream(file);
                    proxyClient.putFile(fis, dst);
                    IOUtils.closeQuietly(fis);
                    proxyClient.deploy(file.getName(), dst, regionName, null, "true", version, "");
                    logger.info("put deploy file " + nodeInfo.getHost() + " success for " + file.getName());
                } else {
                    logger.error(
                            "proxy not online,put deploy file " + nodeInfo.getHost() + " error for " + file.getName());
                }
                proxyClient.stop();
            }
        }
    }

    /**
     * 移除安装包
     *
     * @param args
     * @throws Exception
     */
    public static void operaForRemove(String[] args) throws Exception {
        checkArgs(args, 3);
        String deployName = args[1];
        String regionName = args[2];
        CoreConfParse coreConfParse = new CoreConfParse();
        coreConfParse.parse();
        List<NodeInfo> nodeInfos = coreConfParse.getNodeList();
        for (NodeInfo nodeInfo : nodeInfos) {
            if (SystemMetric.isLocalHost(nodeInfo.getHost())) {
                DeployUtils.removeDeploy(regionName, deployName);
            } else {
                ProxyManager proxyManager = new ProxyManager(coreConfParse.getProxyInfos());
                proxyManager.start(coreConfParse);
                ProxyClient proxyClient = proxyManager.getProxyClient(nodeInfo.getHost());
                if (proxyClient != null && proxyClient.isConnect()) {
                    proxyClient.removeDeploy(regionName, deployName);
                    logger.info("remove deploy " + nodeInfo.getHost() + " success for " + deployName);
                } else {
                    logger.error("proxy not online,remove deploy " + nodeInfo.getHost() + " error for " + deployName);
                }
                proxyClient.stop();
            }
        }
    }

    /**
     * 重新加载配置
     *
     * @param args
     * @throws Exception
     */
    public static void operaForLoadConf(String[] args) throws Exception {
        checkArgs(args, 2);
        String regionName = args[1];
        CoreConfParse conf = new CoreConfParse();
        conf.parse();
        SimpleNode client = new SimpleNode();
        try {
            client.setAutoConnect(false);
            client.setConnectInterval(0);
            client.setConnectLimit(5);
            client.startAsClient(conf.getMasterHosts(), false);
            if (!client.isConnect()) {
                logger.error("connect master fail");
                return;
            }
            JwaterService jwaterService = client.getProxy(JwaterService.class);
            jwaterService.loadConf(regionName);
        } finally {
            client.close();
            NetExecutorUtils.close();
        }
    }

    /**
     * 罗列worker
     *
     * @param args
     * @throws Exception
     */
    public static void operaForListWorker(String[] args) throws Exception {
        checkArgs(args, 2);
        String regionName = args[1];
        CoreConfParse conf = new CoreConfParse();
        conf.parse();
        SimpleNode client = new SimpleNode();
        try {
            client.setAutoConnect(false);
            client.setConnectInterval(0);
            client.setConnectLimit(5);
            client.startAsClient(conf.getMasterHosts(), false);
            if (!client.isConnect()) {
                logger.error("connect master fail");
                return;
            }
            JwaterService jwaterService = client.getProxy(JwaterService.class);
            List<WorkerInfo> workerInfos = jwaterService.getWorkerList(regionName);
            for (WorkerInfo workerInfo : workerInfos) {
                StringBuilder sb = new StringBuilder();
                sb.append(DataKeys.getRunWorkerKey(regionName, workerInfo.getDeployName(), workerInfo.getGroupName(),
                        workerInfo.getWorkerName(), workerInfo.getIndex()));
                sb.append(" ").append(workerInfo.getOnline()).append(" ").append(workerInfo.getHost());
                System.out.println(sb.toString());
            }
        } finally {
            client.close();
            NetExecutorUtils.close();
        }
    }

    /**
     * 罗列job
     *
     * @param args
     * @throws Exception
     */
    public static void operaForListJob(String[] args) throws Exception {
        checkArgs(args, 2);
        String regionName = args[1];
        CoreConfParse conf = new CoreConfParse();
        conf.parse();
        SimpleNode client = new SimpleNode();
        try {
            client.setAutoConnect(false);
            client.setConnectInterval(0);
            client.setConnectLimit(5);
            client.startAsClient(conf.getMasterHosts(), false);
            if (!client.isConnect()) {
                logger.error("connect master fail");
                return;
            }
            JwaterService jwaterService = client.getProxy(JwaterService.class);
            List<JobInfo> jobInfos = jwaterService.getJobList(regionName);
            for (JobInfo jobInfo : jobInfos) {
                StringBuilder sb = new StringBuilder();
                sb.append(DataKeys.getJobKey(regionName, jobInfo.getDeployName(), jobInfo.getGroupName(), jobInfo.getJobName()));
                sb.append(" ").append(jobInfo.isRun()).append(" ").append(jobInfo.getHost());
                System.out.println(sb.toString());
            }
        } finally {
            client.close();
            NetExecutorUtils.close();
        }
    }

    /**
     * 启动控制台
     *
     * @param printConsole
     * @throws Exception
     */
    public static void startConsole(boolean printConsole) throws Exception {
        String className = "com.jwater.console.ConsoleApp";
        CoreConfParse conf = new CoreConfParse();
        conf.parse();
        String jvmArgs = conf.getConsoleJvmArgs();
        List<String> extLibDirs = new ArrayList<String>();
        extLibDirs.add(HomePath.getLaunch());
        String args = className + " " + JvmNames.getConsoleName(conf.getClusterName());
        ProcessUtils.exeClazzName(Jwater.class.getName(), jvmArgs, extLibDirs, args, toProcessCallback(printConsole));
    }

    /**
     * 启动master
     *
     * @param id
     * @param printConsole
     * @throws Exception
     */
    public static void startMaster(int id, boolean printConsole) throws Exception {
        CoreConfParse conf = new CoreConfParse();
        conf.parse();
        String jvmArgs = conf.getMasterJvmArgs();
        List<String> extLibDirs = new ArrayList<String>();
        extLibDirs.add(HomePath.getLaunch());
        String args = MasterMain.class.getName() + " " + id + " " + JvmNames.getMasterName(conf.getClusterName(), id);
        ProcessUtils.exeClazzName(Jwater.class.getName(), jvmArgs, extLibDirs, args, toProcessCallback(printConsole));
    }

    /**
     * 启动proxy
     *
     * @param printConsole
     * @throws Exception
     */
    public static void startProxy(boolean printConsole) throws Exception {
        CoreConfParse conf = new CoreConfParse();
        conf.parse();
        List<String> extLibDirs = new ArrayList<String>();
        extLibDirs.add(HomePath.getLaunch());
        String mainArgs = ProxyMain.class.getName() + " " + JvmNames.getProxyName(conf.getClusterName());
        ProcessUtils.exeClazzName(Jwater.class.getName(), conf.getProxyJvmArgs(), extLibDirs, mainArgs,
                toProcessCallback(printConsole));
    }

    /**
     * 启动worker
     *
     * @param regionName
     * @param deployName
     * @param groupName
     * @param workerName
     * @param index
     * @throws Exception
     */
    public static void startWorker(String regionName, String deployName, String groupName, String workerName, int index)
            throws Exception {
        CoreConfParse conf = new CoreConfParse();
        conf.parse();
        SimpleNode client = new SimpleNode();
        try {
            client.setAutoConnect(false);
            client.setConnectInterval(0);
            client.setConnectLimit(5);
            client.startAsClient(conf.getMasterHosts(), false);
            if (!client.isConnect()) {
                logger.error("connect master fail");
                return;
            }
            JwaterService jwaterService = client.getProxy(JwaterService.class);
            jwaterService.startWorker(regionName, deployName, groupName, workerName, index);
        } finally {
            client.close();
            NetExecutorUtils.close();
        }
    }

    /**
     * 启动job
     *
     * @param regionName
     * @param deployName
     * @param groupName
     * @param jobName
     * @throws Exception
     */
    public static void startJob(String regionName, String deployName, String groupName, String jobName)
            throws Exception {
        CoreConfParse conf = new CoreConfParse();
        conf.parse();
        SimpleNode client = new SimpleNode();
        try {
            client.setAutoConnect(false);
            client.setConnectInterval(0);
            client.setConnectLimit(5);
            client.startAsClient(conf.getMasterHosts(), false);
            if (!client.isConnect()) {
                logger.error("connect master fail");
                return;
            }
            JwaterService jwaterService = client.getProxy(JwaterService.class);
            jwaterService.startJob(regionName, deployName, groupName, jobName);
        } finally {
            client.close();
            NetExecutorUtils.close();
        }
    }

    /**
     * 停止控制台
     */
    public static void stopConsole() {
        CoreConfParse conf = new CoreConfParse();
        conf.parse();
        ProcessUtils.killByClassName(Jwater.class.getName(), JvmNames.getConsoleName(conf.getClusterName()), false);
    }

    /**
     * 停止master
     *
     * @param id
     * @throws Exception
     */
    public static void stopMaster(int id) throws Exception {
        CoreConfParse conf = new CoreConfParse();
        conf.parse();
        String args = JvmNames.getMasterName(conf.getClusterName(), id);
        ProcessUtils.killByClassName(Jwater.class.getName(), args, false);
    }

    /**
     * 停止proxy
     *
     * @throws Exception
     */
    public static void stopProxy() throws Exception {
        CoreConfParse conf = new CoreConfParse();
        conf.parse();
        String param = JvmNames.getProxyName(conf.getClusterName());
        ProcessUtils.killByClassName(Jwater.class.getName(), param, false);
    }

    /**
     * 停止worker
     *
     * @param regionName
     * @param deployName
     * @param groupName
     * @param workerName
     * @param index
     * @throws Exception
     */
    public static void stopWorker(String regionName, String deployName, String groupName, String workerName, int index)
            throws Exception {
        CoreConfParse conf = new CoreConfParse();
        conf.parse();
        SimpleNode client = new SimpleNode();
        try {
            client.setAutoConnect(false);
            client.setConnectInterval(0);
            client.setConnectLimit(5);
            client.startAsClient(conf.getMasterHosts(), false);
            if (!client.isConnect()) {
                logger.error("connect master fail");
                return;
            }
            JwaterService jwaterService = client.getProxy(JwaterService.class);
            jwaterService.stopWorker(regionName, deployName, groupName, workerName, index, false);
        } finally {
            client.close();
            NetExecutorUtils.close();
        }
    }

    /**
     * 停止job
     *
     * @param regionName
     * @param deployName
     * @param groupName
     * @param jobName
     * @throws Exception
     */
    public static void stopJob(String regionName, String deployName, String groupName, String jobName)
            throws Exception {
        CoreConfParse conf = new CoreConfParse();
        conf.parse();
        SimpleNode client = new SimpleNode();
        try {
            client.setAutoConnect(false);
            client.setConnectInterval(0);
            client.setConnectLimit(5);
            client.startAsClient(conf.getMasterHosts(), false);
            if (!client.isConnect()) {
                logger.error("connect master fail");
                return;
            }
            JwaterService jwaterService = client.getProxy(JwaterService.class);
            jwaterService.stopJob(regionName, deployName, groupName, jobName);
        } finally {
            client.close();
            NetExecutorUtils.close();
        }
    }

    /**
     * 运行worker进程
     *
     * @param host
     * @param regionName
     * @param deployName
     * @param groupName
     * @param workerName
     * @param index
     * @param jvmArgs
     * @param printConsole
     * @param envAppend
     * @throws Exception
     */
    public static void runWorker(String host, String regionName, String deployName, String groupName, String workerName,
                                 int index, String jvmArgs, boolean printConsole,
                                 ProcessUtils.ProcessEnvAppend envAppend) throws Exception {
        CoreConfParse conf = new CoreConfParse();
        conf.parse();
        if (StringUtils.isEmpty(host)) {
            host = conf.getLocalNodeInfo().getHost();
        }
        List<String> extLibDirs = new ArrayList<String>();
        extLibDirs.add(HomePath.getLaunch());
        String args = WorkerMain.class.getName() + " " + host + " " + regionName + " " + deployName + " " + groupName
                + " " + workerName + " " + index + " "
                + JvmNames.getWorkerName(conf.getClusterName(), regionName, deployName, groupName, workerName, index);
        ProcessUtils.exeClazzNameEnv(Jwater.class.getName(), jvmArgs, extLibDirs, args, envAppend, toProcessCallback(printConsole));
    }

    /**
     * 运行job
     *
     * @param regionName
     * @param deployName
     * @param groupName
     * @param jobName
     * @param time
     * @param jvmArgs
     * @param printConsole
     * @throws Exception
     */
    public static void runJob(String regionName, String deployName, String groupName, String jobName, long time,
                              String jvmArgs, boolean printConsole) throws Exception {
        CoreConfParse conf = new CoreConfParse();
        conf.parse();
        List<String> extLibDirs = new ArrayList<String>();
        extLibDirs.add(HomePath.getLaunch());
        String args = JobMain.class.getName() + " " + regionName + " " + deployName + " " + groupName + " " + jobName
                + " " + time + " "
                + JvmNames.getJobName(conf.getClusterName(), regionName, deployName, groupName, jobName);
        ProcessUtils.exeClazzName(Jwater.class.getName(), jvmArgs, extLibDirs, args, toProcessCallback(printConsole));
    }

    /**
     * 杀死worker进程
     *
     * @param regionName
     * @param deployName
     * @param groupName
     * @param workerName
     * @param index
     * @param isForce
     * @throws Exception
     */
    public static void killWorker(String regionName, String deployName, String groupName, String workerName, int index,
                                  boolean isForce)
            throws Exception {
        CoreConfParse conf = new CoreConfParse();
        conf.parse();
        String param = JvmNames.getWorkerName(conf.getClusterName(), regionName, deployName, groupName, workerName,
                index);
        ProcessUtils.killByClassName(Jwater.class.getName(), param, isForce);
    }

    /**
     * 杀死job进程
     *
     * @param regionName
     * @param deployName
     * @param groupName
     * @param jobName
     * @throws Exception
     */
    public static void killJob(String regionName, String deployName, String groupName, String jobName)
            throws Exception {
        CoreConfParse conf = new CoreConfParse();
        conf.parse();
        String param = JvmNames.getJobName(conf.getClusterName(), regionName, deployName, groupName, jobName);
        ProcessUtils.killByClassName(Jwater.class.getName(), param, true);
    }
}
